<?php



class Terminal_Check_Socket{



    private $socket  = null;
    private $conn    = null;
    private $chkCode = array( 3 => '软终端', 5 => '自助机', 7 => '外部消费通知');
    private $errCode = array( '101' => '服务器连接失败', '290' => '订单未找到', '519' => '游玩时间未到', '288' => '订单已过期', '293' => '订单已验证',
        '521' => '验证时间未到'
    );


    public static $instance = array();

    // 单例对象
    public static function connect($ipv4='121.40.69.184', $port='2347'){
        $server = md5($ipv4.$port);
        if(isset($instance[$server])) return $instance[$server];
        if(!self::$instance[$server] instanceof self)
            self::$instance[$server] = new self($ipv4, $port);
        return self::$instance[$server];
    }

    // 私有构造连接
    private function __construct($ipv4, $port){
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if($this->socket<0) exit('Create Socket Error.');
        if(!socket_connect($this->socket, $ipv4, $port)) exit('Connect Socket Error.');
    }


    public static function reverse($string, $length = 2){
        $newStr = implode(array_reverse(str_split($string, $length)));
        return $newStr;
    }



    // 配置参数检查
    public function Terminal_Params_Verify(Array $cfg){

        $iConf = array('vMode' => 3, 'vCmd' => 260);
        $iConf = array_merge($iConf, $cfg);

        // 499 绕过验证限制
        if(!in_array($iConf['vCmd'], array(260,499, 498, 289, 288))) return array('state'=>'fail','msg'=>'指令不存在');
        if(!in_array($iConf['vMode'], array(3,5,7,8)))  return array('state'=>'fail','msg'=>'验证方式不正确');

        return array('state'=>'success', 'cfg'=>$iConf);
    }

    // 新旧终端验证方式监测
    public function Terminal_Check_Type_Detect($terminal_num){

        return true;
        // $sql = "select terminal,terminal_type,salerid from uu_land where terminal=$terminal_num limit 1";
        // $GLOBALS['le']->query($sql);
        // $GLOBALS['le']->fetch_assoc();
        // if($GLOBALS['le']->f('terminal_type')==1) return true;

        // 登陆（签到）切换新终端
        // $hexData = unpack('H*', '01,v01.30');
        // $pkgData = $pkgData.$hexData[1];
        // $pkgData = self::Terminal_Data_Package($terminal_num, $pkgData, array('vCmd'=>257, 'vMode'=>8));
        // $clsData = $this->Send_And_Receive(pack('H*' , $pkgData));

        // $stateData = substr($clsData, 28, 4);
        // return ($stateData=='0000') ? true:false;
    }


    // 数据头部公告
    public static function Terminal_Data_Package($terminal_num, $sData, Array $cfg = array()){

        if(!isset($cfg['vMode'])) $cfg['vMode'] = 8;
        $pData  = '4d21';
        $sData .= '00';
        $pLens  = strlen($sData);
        $terHex = str_pad(dechex($pLens), 4, '0', STR_PAD_LEFT);
        $pData .= self::reverse($terHex);// 数据长度
        $pData .= str_pad($hex, 8, $cfg['vMode'], STR_PAD_LEFT); // 填充验证方式 $cfg['vMode']
        // $pData .= str_pad($hex, 8, 8, STR_PAD_LEFT); // 填充验证方式 $cfg['vMode']
        $terHex = str_pad(dechex($terminal_num), 8, '0', STR_PAD_LEFT);
        $pData .= self::reverse($terHex);// 终端号转换
        $terHex = str_pad(dechex($cfg['vCmd']), 4, '0', STR_PAD_LEFT);
        $pData .= self::reverse($terHex);// 指令转换
        $pData  = $pData.'0000'.$sData;
        return $pData;
    }

    // 查询订单
    public function Terminal_Query_Order_Voucher($terminal_num, $salerid, $voucher, Array $cfg = array()){

        // 参数校验
        if(!$terminal_num || !$salerid || !$voucher) return array('state'=>'fail', 'msg'=>'参数错误');

        $cfg['vCmd']  = 288;
        $clsResult = $this->Terminal_Params_Verify($cfg);
        if($clsResult['state']=='fail') return $clsResult;

        if(!isset($cfg['vTerminal_Detect']) || $cfg['vTerminal_Detect']!=0)
            if(!$this->Terminal_Check_Type_Detect($terminal_num)) return array('state'=>'fail', 'msg'=>'新旧终端验证切换失败:'.$terminal_num);

        // 组装数据
        $hexData = unpack('H*', $salerid.$voucher);
        $pkgData = $pkgData.$hexData[1];
        $pkgData = self::Terminal_Data_Package($terminal_num, $pkgData, $clsResult['cfg']);
        $clsData = $this->Send_And_Receive(pack('H*' , $pkgData));

        $resData = substr($clsData, 28, 4);
        if($resData!='0000') return $this->ErrorFormat($resData);

        $relData = substr($clsData, 32, -2);
        $relData = mb_convert_encoding(pack('H*', $relData), 'UTF-8', 'GBK');
        return $relData;
    }

    // 凭证码验证
    public function Terminal_Check_In_Voucher($terminal_num, $salerid, $voucher, Array $cfg = array()){

        // 参数校验
        if(!$terminal_num || !$salerid || !$voucher) return array('state'=>'fail', 'msg'=>'参数错误');

        $clsResult = $this->Terminal_Params_Verify($cfg);
        if($clsResult['state']=='fail') return $clsResult;

        // 新旧终端验证切换监测
        if(!isset($cfg['vTerminal_Detect']) || $cfg['vTerminal_Detect']!=0)
            if(!$this->Terminal_Check_Type_Detect($terminal_num)) return array('state'=>'fail', 'msg'=>'新旧终端验证切换失败');

        // 组装数据
        $hexData = unpack('H*', $salerid.$voucher);
        $pkgData = $pkgData.$hexData[1];
        $pkgData = self::Terminal_Data_Package($terminal_num, $pkgData, $clsResult['cfg']);
        Terminal_Check_Socket::logs($pkgData);
        // exit($pkgData);
        $clsData = $this->Send_And_Receive(pack('H*' , $pkgData));
        return $this->format($clsData);

    }


    // 手机号验证
    public function Terminal_Check_In_Mobile($terminal_num, $salerid, $mobile, Array $cfg = array()){

        // 参数校验
        if(!$terminal_num || !$salerid || !$mobile) return array('state'=>'fail', 'msg'=>'参数错误');

        $clsResult = $this->Terminal_Params_Verify($cfg);
        if($clsResult['state']=='fail') return $clsResult;

        // 新旧终端验证切换监测
        if(!isset($cfg['vTerminal_Detect']) || $cfg['vTerminal_Detect']!=0)
            if(!$this->Terminal_Check_Type_Detect($terminal_num)) return array('state'=>'fail', 'msg'=>'新旧终端验证切换失败');

        // 组装数据
        $hexData = unpack('H*', $salerid.'U'.$mobile);
        $pkgData = $pkgData.$hexData[1];
        $pkgData = self::Terminal_Data_Package($terminal_num, $pkgData, $clsResult['cfg']);
        $clsData = $this->Send_And_Receive(pack('H*' , $pkgData));
        return $this->format($clsData);
    }



    // 凭证码改单验证
    public function Terminal_ChangeCheck_In_Voucher($terminal_num, $salerid, $voucher, $totalTicket, $tiecketData, Array $cfg=array()){

        // 参数校验
        if(!$terminal_num || !$salerid || !$voucher) return array('state'=>'fail', 'msg'=>'参数错误');

        $cfg['vCmd'] = 289;
        $clsResult   = $this->Terminal_Params_Verify($cfg);
        if($clsResult['state']=='fail') return $clsResult;

        // 新旧终端验证切换监测
        if(!isset($cfg['vTerminal_Detect']) || $cfg['vTerminal_Detect']!=0)
            if(!$this->Terminal_Check_Type_Detect($terminal_num)) return array('state'=>'fail', 'msg'=>'新旧终端验证切换失败');

        // 组装数据
        $checkData = '';
        $tickets = array_chunk($tiecketData, 2);
        foreach($tickets as $row) $checkData.= implode(',', $row).'|';
        $checkData = rtrim($checkData, '|');

        $hexData = unpack('H*',$totalTicket.'|'.$salerid.$voucher.'|'.$checkData);
        $pkgData = $pkgData.$hexData[1];
        $pkgData = self::Terminal_Data_Package($terminal_num, $pkgData, $clsResult['cfg']);
        $clsData = $this->Send_And_Receive(pack('H*' , $pkgData));
        return $this->format($clsData);
    }


    // 数据解析
    public function format($hexStr_f){

        $stateData = substr($hexStr_f, 28, 4);
        if($stateData!='0000') return $this->ErrorFormat($stateData);


        $hexStr   = substr($hexStr_f, 32, -2);
        $parseStr = mb_convert_encoding(pack('H*', $hexStr), 'UTF-8', 'GBK');
        $parseArr = explode(',', $parseStr);

        // -1 多单标识
        if($parseArr[0]==-1) return $this->Mulit_Format($hexStr_f);

        $response = array();

        $response['state'] = 'success';
        $response[] = $parseArr[0];  // 网点编号
        $response[] = $parseArr[1];  // 网点名称
        $response[] = $parseArr[2];  // 取票人姓名
        $response[] = $parseArr[3];  // 取票人手机
        $response['ordernum'] = $parseArr[4];  // 订单号
        $response['orderstatus'] = $parseArr[5];  // 订单状态
        $response['ordertotalPrice'] = $parseArr[6];  // 总金额
        $response[] = $parseArr[7];  // 预付金额
        $response[] = $parseArr[8];  // 支付方式
        $response[] = $parseArr[9];  // 商户ID+凭证号
        $response[] = $parseArr[10]; // 预订时间
        $response[] = $parseArr[11]; // 更新时间
        $response[] = $parseArr[12]; // 起始时间
        $response[] = $parseArr[13]; // 终止时间
        $response[] = $parseArr[14]; // 流水号
        $response[] = $parseArr[15]; // 商品种类个数

        if(strpos($parseArr[0],'-2')===0){// 演出类
            $tickets1 = array_slice($parseArr, 17);
            $tickets2 = array_chunk($tickets1, 5);
            $keys = array('ticket_pid', 'ticket_name', 'ticket_price', 'ticket_num', 'ticket_seat');
            foreach($tickets2 as $row) $response['tickets'][] = array_combine($keys, $row);
        }else{
            $tickets1 = array_slice($parseArr, 16);
            $tickets2 = array_chunk($tickets1, 4);
            $keys = array('ticket_pid', 'ticket_name', 'ticket_price', 'ticket_num');
            foreach($tickets2 as $row) $response['tickets'][] = array_combine($keys, $row);
        }
        return $response;
    }


    // 错误格式
    public function ErrorFormat($stateCode){

        $stateCode_a = str_split($stateCode, 2);
        $stateCode_a = array_reverse($stateCode_a);
        $stateCode_b = implode('', $stateCode_a);
        $stateCode_b = hexdec($stateCode_b);
        return array('state'=>'fail', 'code'=>$stateCode_b, 'msg'=>$this->errCode[$stateCode_b]);
    }

    // 返回多单数据
    public function Mulit_Format($hexStr){

        $hexStr   = substr($hexStr, 32);
        $hexStr   = substr($hexStr, 0, -2);
        $parseStr = mb_convert_encoding(pack('H*', $hexStr), 'UTF-8', 'GBK');
        $parseArr = explode(',', $parseStr);
        $tickets1 = array_slice($parseArr, 1);
        $tickets2 = array_chunk($tickets1, 4);
        $response = array('state'=>'success','multi'=>1);
        $keys = array('ticket_name', 'ticket_enddate', 'ticket_num', 'ticket_code');
        foreach($tickets2 as $row) $response['tickets'][] = array_combine($keys, $row);
        return $response;
    }


    // 发送数据并返回数据
    public function Send_And_Receive($senddata){
        // 发送
        if(!socket_write($this->socket, $senddata, strlen($senddata)))
            throw new Exception("socket_write() failed: reason: " . socket_strerror($this->socket) . "\n");

        // 接收
        $hexStr = '';
        while($out = socket_read($this->socket, 8192)){
            $hexStr = bin2hex($out);
            break;
        }
        return $hexStr;
    }

    public function __destruct(){
        socket_close($this->socket);
    }

    public static function logs($content, $file='/var/www/html/new/d/logs/terminal_check_data.log'){
        $fp = fopen($file,"a");
        flock($fp, LOCK_EX);
        fwrite($fp,date("Y-m-d H:i:s").":".$content."\n");
        flock($fp, LOCK_UN);
        fclose($fp);
    }
}